#include "lzw.h"
#include <QFile>
#include <QDataStream>


const int BITS = 12;            //кол-во бит в коде
const int FIRST_BITS = 12;       //нач размер кода
const int LAST_BITS = BITS;     //конеч размер кода
const int MAX_CODE = (1 << BITS)-1; //макс значение кода
const int TABLE_SIZE = 65535;        //кол-во элементов словаря
const int END_OF_STREAM = 256;      //код конца потока
const int INC_CODE_SIZE = 257;      //увеличение разрядности индекса
const int CLEAR_DICT = 258;         //очистка словаря
const int FIRST_CODE = 259;         //первая фраза в словаре
const int UNUSED = -1;              //свободная ячейка в словаре


 unsigned char B,BC,bits=0,BUF=0;

 QFile InFile;
 QFile OutFile;
 QDataStream in(&InFile);
 QDataStream out(&OutFile);

LZW::LZW(QObject *parent) :
    QObject(parent)
{


}
LZW::~LZW(){};

//функция упаковки

void LZW::CompressFile(QString InF, QString OutF)
{
    InFile.setFileName(InF);
    OutFile.setFileName(OutF);

    int next_code;
    qint16 string_code;
    qint16 character;
    unsigned short index;
    unsigned char bit_size;
    unsigned short cur_max_code;
    bool overflow;

    qint8 temp;

    if(!InFile.open(QIODevice::ReadOnly))
    {
        printf("Can't open 5.txt");
        return;
    }

    if(!OutFile.open(QFile::WriteOnly | QFile::Truncate))
    {
        printf("Can't open 6.txt");
        return;
    }


    bit_size=FIRST_BITS;
    cur_max_code=(1<<bit_size)-1;
    next_code=FIRST_CODE;
    for(int i=0;i<TABLE_SIZE;i++)
        d[i].code_value=-1;
    overflow = false;


    printf("Compressing file\n");
//-----------------------------------------------------------------------------------------
    in >> temp;  //считываем первый символ

    string_code = temp;
    string_code = string_code & 0xFF;

    while(!in.atEnd())
    {
        in >> temp;        //считываем символ
        character = temp;
        character = character & 0xFF;

        index = FindDictMatch(string_code, character);  //функция поиска соответствия


    if(d[index].code_value != UNUSED)       //если соответствие найдено
       {
        string_code = d[index].code_value;
        }
        else                                    //иначе заводим новый элемент в словаре
        {
            if(next_code <= MAX_CODE)
            {
                d[index].code_value = next_code;
                next_code++;
                d[index].prefix_code = string_code;
                d[index].ch = char(character);
            }
            //else
                //overflow = true;

                //if (string_code >= cur_max_code)
            if (next_code >= cur_max_code)
            {
                if(bit_size<LAST_BITS)
                {
                    PutBits(INC_CODE_SIZE, bit_size);//выписываем код увеличения строки
                    bit_size++;
                    cur_max_code = (1<<bit_size)-1;
                }
            }


            PutBits(string_code, bit_size);    //выписываем код в кол-во бит
            string_code = character;    //меняем местами символы
/*
            if(overflow)        //при переполнении
            {
                PutBits(CLEAR_DICT, bit_size);  //выписываем код и кол-во бит
                bit_size=FIRST_BITS;
                cur_max_code=(1<<bit_size)-1;
                next_code=FIRST_CODE;
                for(int i=0;i<TABLE_SIZE;i++)
                    d[i].code_value=-1;
                overflow = false;
            }
            */
        }
    }
     PutBits(string_code, bit_size);
     PutBits(END_OF_STREAM, bit_size);
     if(bits) out<<(BUF);


    InFile.close();
    OutFile.close();

return;
}


/*  функция поиска в словаре указанной пары */
unsigned short LZW::FindDictMatch(qint16 aprefix_code,qint16 character)
{

   int index, offset;
/*
      character = character && 0xFF;
      index = (character << (BITS-8));
      index =index xor aprefix_code;//получение хэш-функции
  */


     index = ((aprefix_code>>12)^aprefix_code)&5021;


    if (index == 0) offset = 1;
    else offset = TABLE_SIZE - index;
    do
    {

        if(d[index].code_value == UNUSED) break;
        if(d[index].prefix_code == aprefix_code && d[index].ch == char(character)) break;
        index -=offset;
        if (index < 0) index+=TABLE_SIZE;
    }while(1);

     return index;
}

unsigned short LZW::DecodeString(qint16 count,qint16 code)
{
    while(code > 255)
    {
        decode_stack[count] = d[code].ch;
        count++;
        code = d[code].prefix_code;
    }
    decode_stack[count] = code;
    count++;
    return count;
}

void LZW::DecompressFile(QString InF, QString OutF)
{
    InFile.setFileName(InF);
    OutFile.setFileName(OutF);


    short next_code, old_code, new_code;
    char character;
    unsigned char bit_size;
    unsigned short count, i;

    bit_size = FIRST_BITS;          //инициализация
    next_code = FIRST_CODE-1;
    for(i=0;i<TABLE_SIZE-1;i++)
        d[i].code_value = UNUSED;

    if(!InFile.open(QIODevice::ReadOnly))
    {
        printf("Can't open 6.txt");
        return;
    }

    if(!OutFile.open(QFile::WriteOnly | QFile::Truncate))
    {
        printf("Can't open 7.txt");
        return;
    }

    printf("Decompressing file\n");

    do
    {
        new_code = GetBits(bit_size);


        switch(new_code)
        {
        case CLEAR_DICT:
            {
                 bit_size = FIRST_BITS;          //инициализация
                 next_code = FIRST_CODE;
                 for(i=0;i<TABLE_SIZE-1;i++)
                     d[i].code_value = UNUSED;
                 break;
            }
        case INC_CODE_SIZE:
            {
                 bit_size++;
                 break;
             }
        case 256:
            {
                 InFile.close();
                 OutFile.close();
                 return;
                 break;
             }
        default:
            {
                 if (new_code >= next_code)
                 {
                     decode_stack[0] = character;
                     count = DecodeString(1, old_code);
                 }
                 else count = DecodeString(0, new_code);

                 character = decode_stack[count-1];

                 while(count>0)
                 {
                     count--;
                     PutByte(decode_stack[count]);//putbyte
                 }
                 if (next_code <= MAX_CODE)
                 {
                     d[next_code].prefix_code = old_code;
                     d[next_code].ch = character;
                     next_code++;
                 }

                 old_code = new_code;

                break;
             }
        }


    }while(1);

    return;
}



void LZW::PutBits(unsigned short w, unsigned short count)
{
    unsigned char len;
     len = bits+count;
    if(len<8)
    {
        BUF = BUF | (w << (8-len)); //-> unsigned char
        bits+=count;
    }
    else
    {
        BUF = BUF | (w >> (len-8));
        PutByte(BUF);

        while(len>=16)
        {
            PutByte(w >> (len-16));

            len-=8;
        }
        BUF = (w << (16-len));
        bits = len-8;
    }
}

void LZW::PutByte(unsigned char ch)
{

       out<<ch;
}

unsigned char LZW::GetByte()
{
    qint8 ch;
    in >> ch;       //be ware of minus
    return ch;
}

unsigned short LZW::GetBits(unsigned short count)
{
    unsigned short BW,tempo;
    if (BC==0)
    {
        B = GetByte();
        BC = 8;
    }
    if(count > BC+8)
    {
        BW = 0;
        BW = B;
        BW = BW << 8;
        B = GetByte();
        BW = BW | B;
        B = GetByte();
        tempo = (BW << (count-BC-8)) &
                (0xFFFF >> (16-count) |
                 (B >> (16-count+BC)));
        BC = 16-count+BC;
    }
    else if(count > BC)
    {
        BW = 0;
        BW = B;
        BW = BW << 8;
        B = GetByte();
        BW = BW | B;
        BC+=8;
        tempo = ((BW >> (BC-count)) & (0xFFFF >> (16-count)));
        B = BW & 0xFF;
        BC-=count;
    }
    else
    {
        tempo = ((B >> (BC-count)) & (0xFF >> (8-count)));
        BC-=count;
    }
    return tempo;
}

